//
// File_UNIX.cpp
//
// $Id: //poco/1.4/Foundation/src/File_UNIX.cpp#1 $
//
// Library: Foundation
// Package: Filesystem
// Module:  File
//
// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/File.h"
#include "Poco/Buffer.h"
#include "Poco/Exception.h"
#include "Poco/Path.h"
#include <stdio.h>
#include <algorithm>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <utime.h>
#include <cstring>


namespace Poco {

bool File::exists() const
{
	struct stat st;
	return stat(m_path.c_str(), &st) == 0;
}


bool File::canRead() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
	{
		if (st.st_uid == geteuid())
			return (st.st_mode & S_IRUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IRGRP) != 0;
		else
			return (st.st_mode & S_IROTH) != 0 || geteuid() == 0;
	}
	else handleLastError(m_path);
	return false;
}


bool File::canWrite() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
	{
		if (st.st_uid == geteuid())
			return (st.st_mode & S_IWUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IWGRP) != 0;
		else
			return (st.st_mode & S_IWOTH) != 0 || geteuid() == 0;
	}
	else handleLastError(m_path);
	return false;
}


bool File::canExecute() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
	{
		if (st.st_uid == geteuid() || geteuid() == 0)
			return (st.st_mode & S_IXUSR) != 0;
		else if (st.st_gid == getegid())
			return (st.st_mode & S_IXGRP) != 0;
		else
			return (st.st_mode & S_IXOTH) != 0;
	}
	else handleLastError(m_path);
	return false;
}


bool File::isFile() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return S_ISREG(st.st_mode);
	else
		handleLastError(m_path);
	return false;
}


bool File::isDirectory() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return S_ISDIR(st.st_mode);
	else
		handleLastError(m_path);
	return false;
}


bool File::isLink() const
{
	struct stat st;
	if (lstat(m_path.c_str(), &st) == 0)
		return S_ISLNK(st.st_mode);
	else
		handleLastError(m_path);
	return false;
}


bool File::isDevice() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode);
	else
		handleLastError(m_path);
	return false;
}


bool File::isHidden() const
{
	Path p(m_path);
	p.makeFile();

	return p.getFileName()[0] == '.';
}


Timestamp File::created() const
{
#if defined(__FreeBSD__)
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return Timestamp::fromEpochTime(st.st_birthtime);
#else
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return Timestamp::fromEpochTime(st.st_ctime);
#endif 
	else
		handleLastError(m_path);
	return 0;
}


Timestamp File::getLastModified() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return Timestamp::fromEpochTime(st.st_mtime);
	else
		handleLastError(m_path);
	return 0;
}


File& File::setLastModified(const Timestamp& ts)
{
	struct utimbuf tb;
	tb.actime  = ts.epochTime();
	tb.modtime = ts.epochTime();
	if (utime(m_path.c_str(), &tb) != 0)
		handleLastError(m_path);
    return *this;
}


UInt64 File::getSize() const
{
	struct stat st;
	if (stat(m_path.c_str(), &st) == 0)
		return st.st_size;
	else
		handleLastError(m_path);
	return 0;
}


File& File::setSize(UInt64 size)
{
	if (truncate(m_path.c_str(), size) != 0)
		handleLastError(m_path);
    return *this;
}


File& File::setReadOnly(bool flag)
{
    struct stat st;
    if (stat(m_path.c_str(), &st) != 0) 
        handleLastError(m_path);
    mode_t mode;
    if (flag)
    {
        mode = st.st_mode | S_IWUSR;
    }
    else
    {
        mode_t wmask = S_IWUSR | S_IWGRP | S_IWOTH;
        mode = st.st_mode & ~wmask;
    }
    if (chmod(m_path.c_str(), mode) != 0) 
        handleLastError(m_path);
        return *this;
}

File& File::setWriteable(bool flag)
{
	struct stat st;
	if (stat(m_path.c_str(), &st) != 0) 
		handleLastError(m_path);
	mode_t mode;
	if (flag)
	{
		mode = st.st_mode | S_IRUSR;
	}
	else
	{
		mode_t wmask = S_IRUSR | S_IRGRP | S_IROTH;
		mode = st.st_mode & ~wmask;
	}
	if (chmod(m_path.c_str(), mode) != 0) 
		handleLastError(m_path);
    return *this;
}


File& File::setExecutable(bool flag)
{
	struct stat st;
	if (stat(m_path.c_str(), &st) != 0) 
		handleLastError(m_path);
	mode_t mode;
	if (flag)
	{
		mode = st.st_mode | S_IXUSR;
	}
	else
	{
		mode_t wmask = S_IXUSR | S_IXGRP | S_IXOTH;
		mode = st.st_mode & ~wmask;
	}
	if (chmod(m_path.c_str(), mode) != 0) 
		handleLastError(m_path);
    return *this;
}


void File::copyToFile(const std::string& path) const
{
	int sd = open(m_path.c_str(), O_RDONLY);
	if (sd == -1) handleLastError(m_path);

	struct stat st;
	if (fstat(sd, &st) != 0) 
	{
		close(sd);
		handleLastError(m_path);
	}
	const long blockSize = st.st_blksize;

	int dd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, st.st_mode);
	if (dd == -1)
	{
		close(sd);
		handleLastError(path);
	}
	Buffer<char> buffer(blockSize);
	try
	{
		int n;
		while ((n = read(sd, buffer.begin(), blockSize)) > 0)
		{
			if (write(dd, buffer.begin(), n) != n) 
				handleLastError(path);
		}
		if (n < 0)
			handleLastError(m_path);
	}
	catch (...)
	{
		close(sd);
		close(dd);
		throw;
	}
	close(sd);
	if (fsync(dd) != 0) 
	{
		close(dd);
		handleLastError(path);
	}
	if (close(dd) != 0)
		handleLastError(path);
}


void File::renameTo(const std::string& path)
{
	if (rename(m_path.c_str(), path.c_str()) != 0)
		handleLastError(m_path);
    m_path      = path;
}

void File::remove(bool recursive)
{
    if (recursive && !isLink() && isDirectory())
    {
        std::vector<File> files;
        list(files);
        for (std::vector<File>::iterator it = files.begin(); it != files.end(); ++it)
        {
            it->remove(true);
        }
    }
    // remove
    int rc;
    if (!isLink() && isDirectory())
        rc = rmdir(m_path.c_str());
    else
        rc = unlink(m_path.c_str());
    if (rc) handleLastError(m_path);
}


bool File::createDirectory()
{
	if (exists() && isDirectory())
		return false;
	if (mkdir(m_path.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) != 0) 
		handleLastError(m_path);
	return true;
}


void File::handleLastError(const std::string& path)
{
	switch (errno)
	{
	case EIO:
		throw IOException(path, errno);
	case EPERM:
		throw FileAccessDeniedException("insufficient permissions", path, errno);
	case EACCES:
		throw FileAccessDeniedException(path, errno);
	case ENOENT:
		throw FileNotFoundException(path, errno);
	case ENOTDIR:
		throw OpenFileException("not a directory", path, errno);
	case EISDIR:
		throw OpenFileException("not a file", path, errno);
	case EROFS:
		throw FileReadOnlyException(path, errno);
	case EEXIST:
		throw FileExistsException(path, errno);
	case ENOSPC:
		throw FileException("no space left on device", path, errno);
	case EDQUOT:
		throw FileException("disk quota exceeded", path, errno);
#if !defined(_AIX)
	case ENOTEMPTY:
		throw FileException("directory not empty", path, errno);
#endif
	case ENAMETOOLONG:
		throw PathSyntaxException(path, errno);
	case ENFILE:
	case EMFILE:
		throw FileException("too many open files", path, errno);
	default:
		throw FileException(std::strerror(errno), path, errno);
	}
}

} // namespace Poco
